// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// PLEASE READ BEFORE CHANGING THIS FILE!
//
// This file implements the out of bounds signal handler for
// WebAssembly. Signal handlers are notoriously difficult to get
// right, and getting it wrong can lead to security
// vulnerabilities. In order to minimize this risk, here are some
// rules to follow.
//
// 1. Do not introduce any new external dependencies. This file needs
//    to be self contained so it is easy to audit everything that a
//    signal handler might do.
//
// 2. Any changes must be reviewed by someone from the crash reporting
//    or security team. See OWNERS for suggested reviewers.
//
// For more information, see https://goo.gl/yMeyUY.
//
// This file contains most of the code that actually runs in a signal handler
// context. Some additional code is used both inside and outside the signal
// handler. This code can be found in handler-shared.cc.

#include "src/trap-handler/handler-inside-posix.h"

#include <signal.h>

#ifdef V8_OS_LINUX
#include <ucontext.h>
#elif V8_OS_MACOSX
#include <sys/ucontext.h>
#endif

#include <stddef.h>
#include <stdlib.h>

#include "src/trap-handler/trap-handler-internal.h"
#include "src/trap-handler/trap-handler.h"

namespace v8 {
namespace internal {
    namespace trap_handler {

        bool IsKernelGeneratedSignal(siginfo_t* info)
        {
            // On macOS, only `info->si_code > 0` is relevant, because macOS leaves
            // si_code at its default of 0 for signals that don’t originate in hardware.
            // The other conditions are only relevant for Linux.
            return info->si_code > 0 && info->si_code != SI_USER && info->si_code != SI_QUEUE && info->si_code != SI_TIMER && info->si_code != SI_ASYNCIO && info->si_code != SI_MESGQ;
        }

        class SigUnmaskStack {
        public:
            explicit SigUnmaskStack(sigset_t sigs)
            {
                // TODO(eholk): consider using linux-syscall-support for calling this
                // syscall.
                pthread_sigmask(SIG_UNBLOCK, &sigs, &old_mask_);
            }

            ~SigUnmaskStack() { pthread_sigmask(SIG_SETMASK, &old_mask_, nullptr); }

        private:
            sigset_t old_mask_;

            // We'd normally use DISALLOW_COPY_AND_ASSIGN, but we're avoiding a dependency
            // on base/macros.h
            SigUnmaskStack(const SigUnmaskStack&) = delete;
            void operator=(const SigUnmaskStack&) = delete;
        };

        bool TryHandleSignal(int signum, siginfo_t* info, void* context)
        {
            // Ensure the faulting thread was actually running Wasm code. This should be
            // the first check in the trap handler to guarantee that the IsThreadInWasm
            // flag is only set in wasm code. Otherwise a later signal handler is executed
            // with the flag set.
            if (!IsThreadInWasm()) {
                return false;
            }

            // Clear g_thread_in_wasm_code, primarily to protect against nested faults.
            //g_thread_in_wasm_code = false;
            ClearThreadInWasm();

            // Bail out early in case we got called for the wrong kind of signal.

            if (signum != kOobSignal) {
                return false;
            }

            // Make sure the signal was generated by the kernel and not some other source.
            if (!IsKernelGeneratedSignal(info)) {
                return false;
            }

            // Begin signal mask scope. We need to be sure to restore the signal mask
            // before we restore the g_thread_in_wasm_code flag.
            {
                // Unmask the signal so that if this signal handler crashes, the crash will
                // be handled by the crash reporter.  Otherwise, the process might be killed
                // with the crash going unreported.
                sigset_t sigs;
                // Fortunately, sigemptyset and sigaddset are async-signal-safe according to
                // the POSIX standard.
                sigemptyset(&sigs);
                sigaddset(&sigs, SIGSEGV);
                SigUnmaskStack unmask(sigs);

                ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
#if V8_OS_LINUX
                auto* context_rip = &uc->uc_mcontext.gregs[REG_RIP];
#elif V8_OS_MACOSX
                auto* context_rip = &uc->uc_mcontext->__ss.__rip;
#else
#error Unsupported platform
#endif
                uintptr_t fault_addr = *context_rip;
                uintptr_t landing_pad = 0;
                if (TryFindLandingPad(fault_addr, &landing_pad)) {
                    // Tell the caller to return to the landing pad.
                    *context_rip = landing_pad;
                    // We will return to wasm code, so restore the g_thread_in_wasm_code flag.
                    //g_thread_in_wasm_code = true;
                    SetThreadInWasm();
                    return true;
                }
            } // end signal mask scope

            // If we get here, it's not a recoverable wasm fault, so we go to the next
            // handler. Leave the g_thread_in_wasm_code flag unset since we do not return
            // to wasm code.
            return false;
        }

        void HandleSignal(int signum, siginfo_t* info, void* context)
        {
            if (!TryHandleSignal(signum, info, context)) {
                // Since V8 didn't handle this signal, we want to re-raise the same signal.
                // For kernel-generated SEGV signals, we do this by restoring the original
                // SEGV handler and then returning. The fault will happen again and the
                // usual SEGV handling will happen.
                //
                // We handle user-generated signals by calling raise() instead. This is for
                // completeness. We should never actually see one of these, but just in
                // case, we do the right thing.
                RemoveTrapHandler();
                if (!IsKernelGeneratedSignal(info)) {
                    raise(signum);
                }
            }
            // TryHandleSignal modifies context to change where we return to.
        }

        void RemoveTrapHandler()
        {
        }

        bool RegisterDefaultTrapHandler() { return false; }

    } // namespace trap_handler
} // namespace internal
} // namespace v8
